package com.googlecode.nls4j;

import java.io.Serializable;
import java.text.FieldPosition;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

/**
 * This class is useful for creating mixed bundles that contain both 'formatted'
 * and 'non-formatted' text. In fact it's a wrapper for a text with several
 * utility methods. Since the {@linkplain MessageFormat} object for the wrapped
 * text is created lazy, only on first invocation of the
 * {@link #getMessageFormat()} method, this wrapper does not impact on
 * 'non-formatted' text.<br/>
 * <br/>
 * 
 * Usage example:
 * 
 * <pre>
 * public enum Msg implements KeyValuePair&lt;Text&gt; {
 *     Hello(&quot;Hello&quot;),
 *     Bye(&quot;Bye&quot;),
 *     Time(&quot;The time is {0,time}&quot;);
 * 
 *     private static final CurrentBundle&lt;Msg, Text&gt; currentBundle = CurrentBundle
 *             .newInstance(
 *                 NlsContext.getDefault(),
 *                 new KeyValuePairStorage&lt;Msg, Text&gt;(
 *                         Msg.class,
 *                         EnumTextBundleLoader.&lt;Msg&gt; getDefault(false), EnumSet
 *                                 .allOf(Msg.class)), false);
 *     private final Text defaultValue;
 *     private volatile Text value;
 * 
 *     private Msg(final String defaultValue) {
 *         this.defaultValue = value = new Text(defaultValue, Locale.ENGLISH);
 *     }
 * 
 *     public static CurrentBundle&lt;Msg, Text&gt; getCurrentBundle() {
 *         return currentBundle;
 *     }
 * 
 *     public String getName() {
 *         return name();
 *     }
 * 
 *     public Text getDefaultValue(final Locale locale) {
 *         return defaultValue;
 *     }
 * 
 *     public Text getValue() {
 *         return value;
 *     }
 * 
 *     public void setValue(final Text value) {
 *         this.value = value;
 *     }
 * 
 *     public String format(final Object... args) {
 *         return value.format(args);
 *     }
 * 
 *     public String toString() {
 *         return value.toString();
 *     }
 * }
 * 
 * public static void main(final String[] args) {
 *     System.out.println(Msg.Hello);
 *     System.out.println(Msg.Time.format(new Date()));
 * }
 * 
 * </pre>
 * 
 * @author Andrey Pavlenko
 */
public class Text implements Serializable, Cloneable, Comparable<Text> {
    private static final long serialVersionUID = 144573667696701252L;
    private static final AtomicReferenceFieldUpdater<Text, MessageFormat> ref = AtomicReferenceFieldUpdater
            .newUpdater(Text.class, MessageFormat.class, "messageFormat");
    private final String text;
    private final Locale locale;
    private volatile MessageFormat messageFormat;

    /**
     * Create new Text instance with the specified text and locale.
     * 
     * @throws NullPointerException
     *             if one of the argument is null.
     */
    public Text(final String text, final Locale locale)
            throws NullPointerException {
        if (text == null) {
            throw new NullPointerException();
        }
        if (locale == null) {
            throw new NullPointerException();
        }
        this.text = text;
        this.locale = locale;
    }

    /**
     * Returns the locale specified in the constructor.
     */
    public Locale getLocale() {
        return locale;
    }

    /**
     * Returns the {@linkplain MessageFormat} object for the wrapped text. The
     * MessageFormat object is create only once (on the first invocation of this
     * method) using the {@link #createMessageFormat()} method. Subsequent
     * invocations returns the same object, so the returned object must not be
     * modified.
     */
    public MessageFormat getMessageFormat() {
        MessageFormat f = messageFormat;

        if (f == null) {
            f = createMessageFormat();

            if (!ref.compareAndSet(this, null, f)) {
                return messageFormat;
            }
        }

        return f;
    }

    /**
     * A shorthand for {@code getMessageFormat().format(args)}.
     */
    public String format(final Object... args) {
        return getMessageFormat().format(args, new StringBuffer(),
            new FieldPosition(0)).toString();
    }

    /**
     * Create new {@linkplain MessageFormat} object. The default implementation
     * returns {@code new MessageFormat(toString(), getLocale())}. Subclasses
     * may override this method.
     */
    protected MessageFormat createMessageFormat() {
        return new MessageFormat(toString(), getLocale());
    }

    @Override
    public int compareTo(final Text o) {
        return text.compareTo(o.text);
    }

    @Override
    public int hashCode() {
        return text.hashCode();
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof Text) {
            return text.equals(((Text) obj).text);
        }
        return false;
    }

    /**
     * Returns the wrapped text.
     */
    @Override
    public String toString() {
        return text;
    }
}
